#include "test.h"

#include <unistd.h>

#include <stdio.h>
#include <stdlib.h>

#include <pthread.h>
#include <ctype.h>

#include <time.h>

pthread_t* thread_id;
pthread_barrier_t barrier;

struct timespec tp[3];

struct argument* arg;

//test parallel matrix multiplication implementation
void test_parl_mult(int thread_num, int size, int seed, int loop_num ,int mode, int debug_flag)
{
	int i = 0;

	//if it's only a single-processor call
	//invoke the sequential implemetation function
	if (thread_num == 1)
	{
		test_seq_mult(size, seed, loop_num, mode, debug_flag);	

		return;
	}

	//set a barrial for timing
	pthread_barrier_init(&barrier, NULL, thread_num);

	int** left = NULL;
	int** right = NULL;
	int** result = NULL;

	int** temp = NULL;
	temp = (int** )malloc(size * sizeof(int* ));
	for (i = 0; i < size; i++)
	{
		temp[i] = (int* )malloc(size * sizeof(int));	
	}


	arg = (struct argument* )malloc(thread_num * sizeof(struct argument));

	//create and iniate the matrix
	create_matrix(&left, &right, &result, size);
	initiate_matrix(left, right, size, seed);

	thread_id = (pthread_t* )malloc(thread_num * sizeof(pthread_t));

	int interval = size / thread_num;

	/*
	(void* )thread_func(void* ) == NULL;

	switch(mode)
	{
		case 'n':
		{
			thread_func = thread_normal_mult;	

			break;
		}
		case 'a':
		{
			thread_func = thread_adv_mult;	

			break;
		}
		default:
		{
			break;	
		}
	}
	*/

	//allocate work for each thread
	for (i = 1; i < thread_num; i++)	
	{
		arg[i].id = i;

		thread_id[i] = (pthread_t)(i);

		arg[i].head = interval * i;
		arg[i].rear = (i < (thread_num-1)) ? (interval * (i+1)) : (size);

		arg[i].left = left;
		arg[i].right = right;
		arg[i].result = result;

		arg[i].temp = temp;

		arg[i].size = size;

		arg[i].loop_num = loop_num;

		arg[i].mode = mode;

		pthread_create(thread_id+i, NULL, thread_mult, (void* )(arg+i));	
	}

	//allocate work for master thread
	arg[0].id = 0;

	thread_id[0] = (pthread_t)(0);

	arg[0].head = 0;
	arg[0].rear = interval;

	arg[0].left = left;
	arg[0].right = right;
	arg[0].result = result;

	arg[0].temp = temp;

	arg[0].size = size;

	arg[0].loop_num = loop_num;

	arg[0].mode = mode;

	thread_mult((void* )(arg));

	free(thread_id);
	pthread_barrier_destroy(&barrier);

	//print out the run time
	switch(mode)
	{
		case 'n':	
		{
			printf("\n\nnormal parallel matrix multiplication\n");	

			double sec = tp[1].tv_sec - tp[0].tv_sec;
			double nsec = tp[1].tv_nsec - tp[0].tv_nsec;

			for(i = 0; i < 9; i++) 
			{
				sec *= 10;	
			}
			nsec += sec;

			printf("processor #: %d, loop #: %d, matrix size: %d",1, loop_num, size);

			printf("\ntime costs: %e nsecs\n", nsec);
			printf("ave time costs: %e nsecs\n", nsec/loop_num);

			break;
		}
		case 'a':
		{
		
			printf("\n\nadvanced parallel matrix multiplication\n");	

			double sec = tp[0].tv_sec - tp[2].tv_sec;
			double nsec = tp[0].tv_nsec - tp[2].tv_nsec;

			for(i = 0; i < 9; i++) 
			{
				sec *= 10;	
			}
			nsec += sec;

			int o_nsec = nsec;

			printf("processor #: %d, loop #: %d, matrix size: %d",1, loop_num, size);

			printf("\ntranspose-time costs: %e nsecs\n", nsec);
			printf("ave transpose-time costs: %e nsecs\n", nsec/loop_num);

			sec = tp[1].tv_sec - tp[0].tv_sec;
			nsec = tp[1].tv_nsec - tp[0].tv_nsec;

			for(i = 0; i < 9; i++) 
			{
				sec *= 10;	
			}
			nsec += sec;

			printf("\nmultiplication-time costs: %e nsecs\n", nsec);
			printf("ave multiplication-time costs: %e nsecs\n", nsec/loop_num);

			printf("\ntime costs: %e nsecs\n", nsec+o_nsec);
			printf("ave time costs: %e nsecs\n", (nsec+o_nsec)/loop_num);

			break;
		}
		default:
		{
			break;	
		}
	}


	//for debug use
	//print out the matrix
	if (debug_flag != 0)
	{
		int i = 0;
		int j = 0;

		printf("\n\nnow the result matrix\n\n");

		for (i = 0; i < size; i++)
		{
			printf("\n");

			for (j = 0; j < size; j++)	
			{
				printf("%-10d\t", result[i][j]);	
			}
		}
		
		
		/*
		printf("\n\nnow the left matrix\n\n");

		for (i = 0; i < size; i++)
		{
			printf("\n");

			for (j = 0; j < size; j++)	
			{
				printf("%-10d\t", left[i][j]);	
			}
		}

		printf("\n\nnow the right matrix\n\n");

		for (i = 0; i < size; i++)
		{
			printf("\n");

			for (j = 0; j < size; j++)	
			{
				printf("%-10d\t", right[i][j]);	
			}
		}
	
		*/

		printf("\n\n");

	}

	//sure, free the memory I requested from sys
	recycle_matrix(left, right, result, size);

	free(arg);

	for (i = 0; i < size; i++)
	{
		free(temp[i]);	
	}
	free(temp);
}

/*
//concrete multiplicatin implementation, it's the most naive implementation
void* thread_normal_mult(void* arg)
{
	//retrieve argument
	int** result = ((struct argument* )(arg))->result;

	int** left = ((struct argument* )(arg))->left;
	int** right = ((struct argument* )(arg))->right;

	int size = ((struct argument* )(arg))->size;

	int head = ((struct argument* )(arg))->head;
	int rear = ((struct argument* )(arg))->rear;

	int loop_num = ((struct argument* )(arg))->loop_num;

	int mode = ((struct argument* )(arg))->mode;

	int i = 0;
	int j = 0;
	int k = 0;

	int res = 0;

	int count = 0;

	//get all the threads ready for timing
	pthread_barrier_wait(&barrier);
	if (((struct argument*)(arg))->id == 0)
	{
		clock_gettime(CLOCK_REALTIME, tp);	
	}

	for (count = 0; count < loop_num; count++)
	{
		//fine,let's work
		for (i = head; i < rear; i++)
		{
			for (j = 0; j < size; j++)	
			{
				res = 0;

				for (k = 0; k < size; k++)	
				{
					res += (left[i][k] * right[k][j]);
				}

				result[i][j] = res;
			}

		}

		pthread_barrier_wait(&barrier);
	}

	//get all the threads ready for timing
	//pthread_barrier_wait(&barrier);
	if (((struct argument* )(arg))->id == 0)
	{
		clock_gettime(CLOCK_REALTIME, tp + 1);	
	}


	//master thread cant exit, it must clean the mess, like free requested memory
	//all other irrelevan threads exit
	if (((struct argument*)(arg))->id != 0)
	{//non-master thread exits
		pthread_exit(NULL);
	}
}
*/

//concrete multiplicatin implementation, it's advanced implementation
void* thread_mult(void* arg)
{
	//retrieve argument
	int** result = ((struct argument* )(arg))->result;

	int** left = ((struct argument* )(arg))->left;
	int** right = ((struct argument* )(arg))->right;

	int** temp = ((struct argument* )(arg))->temp;

	int size = ((struct argument* )(arg))->size;

	int head = ((struct argument* )(arg))->head;
	int rear = ((struct argument* )(arg))->rear;

	int loop_num = ((struct argument* )(arg))->loop_num;

	int mode = ((struct argument* )(arg))->mode;

	int i = 0;
	int j = 0;
	int k = 0;

	int res = 0;

	int count = 0;
	
	switch(mode)
	{
		case 'n':
		{
			//get all the threads ready for timing
			pthread_barrier_wait(&barrier);
			if (((struct argument*)(arg))->id == 0)
			{
				clock_gettime(CLOCK_REALTIME, tp);	
			}

			for (count = 0; count < loop_num; count++)
			{
				//fine,let's work
				for (i = head; i < rear; i++)
				{
					for (j = 0; j < size; j++)	
					{
						res = 0;

						for (k = 0; k < size; k++)	
						{
							res += (left[i][k] * right[k][j]);
						}

						result[i][j] = res;
					}

				}

				pthread_barrier_wait(&barrier);
			}

			//get all the threads ready for timing
			//pthread_barrier_wait(&barrier);
			if (((struct argument* )(arg))->id == 0)
			{
				clock_gettime(CLOCK_REALTIME, tp + 1);	
			}

			break;
		}
		case 'a':
		{
			//get all the threads ready for timing
			pthread_barrier_wait(&barrier);
			if (((struct argument*)(arg))->id == 0)
			{
				clock_gettime(CLOCK_REALTIME, tp+2);	
			}

			for (count = 0; count < loop_num; count++)
			{
				for (i = head; i < rear; i++)
				{
					for (j = 0; j < size; j++)	
					{
					
						temp[j][i] = right[i][j];
					}
				}

				pthread_barrier_wait(&barrier);
			}

			//get all the threads ready for timing
			pthread_barrier_wait(&barrier);
			if (((struct argument*)(arg))->id == 0)
			{
				clock_gettime(CLOCK_REALTIME, tp);	
			}

			for (count = 0; count < loop_num; count++)
			{
				//fine,let's work
				for (i = head; i < rear; i++)
				{
					for (j = 0; j < size; j++)	
					{
						res = 0;

						for (k = 0; k < size; k++)	
						{
							res += (left[i][k] * temp[j][k]);
						}

						result[i][j] = res;
					}

				}

				pthread_barrier_wait(&barrier);
			}

			//get all the threads ready for timing
			//pthread_barrier_wait(&barrier);
			if (((struct argument* )(arg))->id == 0)
			{
				clock_gettime(CLOCK_REALTIME, tp + 1);	
			}

			break;
		}
		default:
		{
			break;	
		}
	}



	//master thread cant exit, it must clean the mess, like free requested memory
	//all other irrelevan threads exit
	if (((struct argument*)(arg))->id != 0)
	{//non-master thread exits
		pthread_exit(NULL);
	}
}

//test sequential matrix multiplication implementation
void test_seq_mult(int size, int seed, int loop_num, int mode, int debug_flag)
{
	//retrieve argument
	int** result = NULL;

	int** left = NULL;
	int** right = NULL;

	int i = 0;
	int j = 0;
	int k = 0;

	int res = 0;

	int count = 0;

	create_matrix(&left, &right, &result, size);
	initiate_matrix(left, right, size, seed);

	switch(mode)
	{
		case 'n':
		{
		
			//start timing
			clock_gettime(CLOCK_REALTIME, tp);	

			for (count = 0; count < loop_num; count++)
			{

				//fine,let's work
				for (i = 0; i < size; i++)
				{
					for (j = 0; j < size; j++)	
					{
						res = 0;

						for (k = 0; k < size; k++)	
						{
							res += (left[i][k] * right[k][j]);
						}

						result[i][j] = res;
					}
				} 
			}	

			//stop timing
			clock_gettime(CLOCK_REALTIME, tp+1);	

			printf("\n\nnormal sequential matrix multiplication\n");	

			double sec = tp[1].tv_sec - tp[0].tv_sec;
			double nsec = tp[1].tv_nsec - tp[0].tv_nsec;

			for(i = 0; i < 9; i++) 
			{
				sec *= 10;	
			}
			nsec += sec;

			printf("processor #: %d, loop #: %d, matrix size: %d",1, loop_num, size);

			printf("\ntime costs: %e nsecs\n", nsec);
			printf("ave time costs: %e nsecs\n", nsec/loop_num);

			break;
		} 
		case 'a':
		{
		
			//start timing
			clock_gettime(CLOCK_REALTIME, tp+2);	

			for (count = 0; count < loop_num; count++)
			{

				//fine,let's work
				for (i = 0; i < size; i++)
				{
					res = right[i][j];
					right[i][j] = right[j][i];
					right[j][i] = res;
				} 
			}	

			//stop timing
			clock_gettime(CLOCK_REALTIME, tp);	

			for (count = 0; count < loop_num; count++)
			{

				//fine,let's work
				for (i = 0; i < size; i++)
				{
					for (j = 0; j < size; j++)	
					{
						res = 0;

						for (k = 0; k < size; k++)	
						{
							res += (left[i][k] * right[j][k]);
						}

						result[i][j] = res;
					}
				} 
			}	

			//stop timing
			clock_gettime(CLOCK_REALTIME, tp+1);	

			printf("\n\nadvanced sequential matrix multiplication\n");	

			double sec = tp[0].tv_sec - tp[2].tv_sec;
			double nsec = tp[0].tv_nsec - tp[2].tv_nsec;

			for(i = 0; i < 9; i++) 
			{
				sec *= 10;	
			}
			nsec += sec;

			printf("processor #: %d, loop #: %d, matrix size: %d",1, loop_num, size);

			printf("\ntransverse-time costs: %e nsecs\n", nsec);
			printf("ave transverse-time costs: %e nsecs\n", nsec/loop_num);

			double o_nsec = nsec;

			sec = tp[1].tv_sec - tp[0].tv_sec;
			nsec = tp[1].tv_nsec - tp[0].tv_nsec;

			for(i = 0; i < 9; i++) 
			{
				sec *= 10;	
			}
			nsec += sec;

			printf("\nmultiplication-time costs: %e nsecs\n", nsec);
			printf("ave multiplication-time costs: %e nsecs\n", nsec/loop_num);

			printf("\ntime costs: %e nsecs\n", nsec + o_nsec);
			printf("ave time costs: %e nsecs\n", (nsec+o_nsec)/loop_num);

			break;
		}
		default:
		{
			break;	
		}
	}


	//for debug use
	//print out the matrix
	if (debug_flag != 0)
	{
		int i = 0;
		int j = 0;

		printf("\n\nnow the result matrix\n\n");

		for (i = 0; i < size; i++)
		{
			printf("\n");

			for (j = 0; j < size; j++)	
			{
				printf("%-10d\t", result[i][j]);	
			}
		}
		
		/*

		printf("\n\nnow the left matrix\n\n");

		for (i = 0; i < size; i++)
		{
			printf("\n");

			for (j = 0; j < size; j++)	
			{
				printf("%-10d\t", left[i][j]);	
			}
		}

		printf("\n\nnow the right matrix\n\n");

		for (i = 0; i < size; i++)
		{
			printf("\n");

			for (j = 0; j < size; j++)	
			{
				printf("%-10d\t", right[i][j]);	
			}
		}
		
		*/

		printf("\n\n");

	}

	recycle_matrix(left, right, result, size);
}

void debug_mult(int thread_num, int size, int seed)
{
	test_parl_mult(thread_num, size, seed, 1, 'a', 1);
	test_parl_mult(thread_num, size, seed, 1, 'n', 1);

	test_seq_mult(size, seed, 1, 'a', 1);
	test_seq_mult(size, seed, 1, 'n', 1);

}
